package lambdasinaction.chap8.my;

import java.util.ArrayList;
import java.util.List;

/**
 * 观察者模式 是一种比较常见的方案，某些事件发生时（比如状态转变），如果一个对象（通常我们称为主题）需要自动地通知其他多个对象（称为观察者），就会采用该方案。
 * 创建图形用户界面（GUI）程序时，你经常会使用该设计模式。 这种情况下，你会在图形用户界面组件（比如按钮）上注册一系列的观察者。 如果点击按钮，观察者就会收到通知，并随即执行某个特定的行为。
 * 但是观察者模式并不局限于图形用户界面。 比如，观察者设计模式也适用于股票交易的情形， 多个券商可能都希望对某一支股票价格（主题）的变动做出响应。 让我们写点代码来看看观察者模式在实际中多么有用。
 * 你需要为 Twitter 这样的应用设计并实现一个定制化的通知系统。 想法很简单：好几家报纸机构，如果《纽约时报》《卫报》以及《世界报》都订阅了新闻，他们希望当接收的新闻中包含他们感兴趣的关键字时，能得到特别通知。
 *
 * @author Fish
 * @since 0.0.1 2018/8/8 17:54
 */
public class MyObserverMain {

  /**
   * 首先，你需要一个观察者接口，它将不同的观察者聚合在一起。 它仅有一个名为 notify 的方法，一旦接收到一条新的新闻，该方法就会被调用
   */
  interface Observer {

    void notify(String tweet);
  }

  /**
   * 你还遗漏了最重要的部分：Subject! 让我们为它定义一个接口 Subject 使用 registerObserver 方法可以注册一个新的观察者，使用 notifyObservers
   * 方法通知它的观察者一个新闻的到来。
   */
  interface Subject {

    void registerObserver(Observer observer);

    void notifyObservers(String tweet);
  }

  /**
   * 现在，你可以声明不同的观察者（比如，这里是三家不同的报纸机构），依据新闻中不同的关键字分别定义不同的行为
   * 纽约时报
   */
  static private class NYTimes implements Observer {

    @Override
    public void notify(String tweet) {
      if (null != tweet && tweet.contains("money")) {
        System.out.println("Breaking news in NY! " + tweet);
      }
    }
  }

  /**
   * 卫报
   */
  static private class Guardian implements Observer {

    @Override
    public void notify(String tweet) {
      if (null != tweet && tweet.contains("queen")) {
        System.out.println("Yet another news in London..." + tweet);
      }
    }
  }

  /**
   * 世界报
   */
  static private class Lemonde implements Observer {

    @Override
    public void notify(String tweet) {
      if (null != tweet && tweet.contains("wine")) {
        System.out.println("Today cheese, wine and news!" + tweet);
      }
    }
  }

  /**
   * 这是一个非常直观的实现：Feed类在内部维护了一个观察者列表，一条新闻到达时，它就进行通知
   */
  static private class Feed implements Subject {

    private final List<Observer> observers = new ArrayList<>();

    @Override
    public void registerObserver(Observer observer) {
      this.observers.add(observer);
    }

    @Override
    public void notifyObservers(String tweet) {
      this.observers.forEach(o -> o.notify(tweet));
    }
  }

  public static void main(String[] args) {
    Feed f = new Feed();
    f.registerObserver(new NYTimes());
    f.registerObserver(new Guardian());
    f.registerObserver(new Lemonde());
    f.notifyObservers("The queen said her favourite book is Java 8 in Action!");

    /**
     * 使用 Lambda 表达式
     * 你可能会疑惑 Lambda 表达式在观察者设计模式中如何发挥它的作用。
     * 不知道你有没有注意到，Observer 接口的所有实现类都提供了一个方法：notify。
     * 新闻到达时，它们都只是对同一段代码封装执行。
     * Lambda 表达式的设计初衷就是要消除这样的僵化代码。
     * 使用 Lambda 表达式后，你无需显式地实例化三个观察者对象，直接传递 Lambda 表达式表示需要执行的行为即可：
     */
    f.registerObserver((String tweet) -> {
      if (null!=tweet && tweet.contains("money")) {
        System.out.println("Breaking news in NY!" + tweet);
      }
    });

    f.registerObserver((String tweet) -> {
      if (null!=tweet && tweet.contains("queen")) {
        System.out.println("Yet another news in London..." + tweet);
      }
    });
    /**
     * 那么，是否我们随时随地都可以使用 Lambda 表达式呢？
     * 答案是否定的！
     * 我们前文介绍的例子中，Lambda 适配得很好，那是因为需要执行的动作都很简单，
     * 因此才能很方便地消除僵化代码。
     * 但是，观察者的逻辑有可能十分复杂，它们可能还持有状态，抑或者定义了多个方法，诸如此类。
     * 在这些情形下，你还是应该继续使用类的方式。
     */
  }
}
